#!/usr/bin/python3

###############################################################################
#                                                                             #
#    L3q - Light, light, lightweight queue                                    #
#    Copyright (C) 2023  Marcus Pedersén marcus.pedersen@slu.se               #
#                                                                             #
#    This program is free software: you can redistribute it and/or modify     #
#    it under the terms of the GNU General Public License as published by     #
#    the Free Software Foundation, either version 3 of the License, or        #
#    (at your option) any later version.                                      #
#                                                                             #
#    This program is distributed in the hope that it will be useful,          #
#    but WITHOUT ANY WARRANTY; without even the implied warranty of           #
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            #
#    GNU General Public License for more details.                             #
#                                                                             #
#    You should have received a copy of the GNU General Public License        #
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.    #
#                                                                             #
###############################################################################


import os
import pwd
import sys
import argparse
import textwrap
import requests
import json
import getpass
import logging
import shutil
import urllib3
from urllib3.exceptions import InsecureRequestWarning
from libl3q.client.client import L3QConfig
from libl3q.common.common import L3QAction
from libl3q.common.version import VERSION, ascii_l3q
from typing import Mapping, Union, Any

def version() -> None:
    '''
    Command entered: l3q --version .
    '''

    ascii_l3q()
    print('l3q {}'.format(VERSION))
    print('Copyright (C) 2023-2024  Marcus Pedersén')
    print('License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.')
    print('This is free software: you are free to change and redistribute it.')
    print('There is NO WARRANTY, to the extent permitted by law.')
    print('')
    print('Written by Marcus Pedersén.')



    

def wrong_args(args: argparse.Namespace) -> None:
    '''
    Called when wrong arguments are used.
    '''
    print('Sub-command is missing.')
    print('Please try -h or --help for help.')


def process_add(args: argparse.Namespace) -> None:
    '''
    Sub-command process add
    Command entered: l3q add <args>
    '''

    if(args.taskfile):
        if(not os.path.exists(args.taskfile)):
            print('Error task file: {} does not exist.'.format(args.taskfile), file=sys.stderr)
            exit(1)

        para  = False
        seq = False
        try:
            with open(args.taskfile) as f:
                l: str = f.readline()
                while(len(l) > 0):
                    l = l.strip()

                    if(l.startswith('#--add-para')):
                        para = True

                    if(l.startswith('#--add-seq')):
                        seq = True

                    l = f.readline()
        except Exception as e:
            print('Error reading task file: {}'.format(args.taskfile),file=sys.stderr)
            logging.error('User: {}, Error reading task file: {}. Error: {}'.format(os.getlogin(), args.taskfile, e))
            exit(1)

        args.TASKFILE = args.taskfile
        args.cores = False
        args.nodes = False
        args.jobname = ''
        if(para and seq):
            print('Error, TASKFILE contains a special syntax line for both sequence and parallel.', file=sys.stdout)
            print('Review file: {}'.format(args.taskfile), file=sys.stderr)
            exit(2)
        elif(para):
            process_add_para(args)
        elif(seq):
            process_add_seq(args)
        else:
            print('Error, TASKFILE does not contain a special syntax line.', file=sys.stdout)
            print('Review file: {}'.format(args.taskfile), file=sys.stderr)
            exit(3)
    else:
        print('usage: l3q add para [-h] -t TASKFILE', file=sys.stderr)
        print('l3q add: error: the following arguments are required: -t TASKFILE', file=sys.stderr)
        print('Alternative use: l3q add para', file=sys.stderr)
        print('or: l3q add seq', file=sys.stderr)
        exit(4)

    
    
    
def process_add_para(args: argparse.Namespace) -> None:
    '''
    Sub-command process add para
    Command entered: l3q add para <args>
    '''

    if(not os.path.exists(args.TASKFILE)):
        print('Error task file: {} does not exist.'.format(args.TASKFILE), file=sys.stderr)
        exit(1)

    tasks = []
    jobname = ''
    nodes  = -1
    cores = -1
    depend = ''
    try:
        with open(args.TASKFILE) as f:
            l = f.readline()
            while(len(l) > 0):
                l =l.strip()

                if(l.startswith('#--add-para')):
                    if(l.startswith('#--add-para ')):
                        iname = l.find('--jobname')
                        if(iname == -1):
                            iname = l.find('-j')

                        icores = l.find('--cores')
                        if(icores == -1):
                            icores = l.find('-c')

                        inodes = l.find('--nodes')
                        if(inodes == -1):
                            inodes = l.find('-n')

                        ijobid = l.find('--jobid')
                        if(ijobid != -1):
                            args.jobid = True
                        else:
                            ijobid = l.find('-i')
                            if(ijobid != -1):
                                args.jobid = True

                        idepend = l.find('--depend')
                        if(idepend == -1):
                            idepend = l.find('-d')
                                
                        iargs = [iname, icores, inodes, ijobid, idepend]
                        iargs.sort()

                        sargs =[]
                        i = 0
                        while(i<=4):
                            if(iargs[i] != -1):
                                if(i!=4):
                                    sargs.append(l[iargs[i]:iargs[i+1]])
                                else:
                                    sargs.append(l[iargs[i]:])
                            i += 1
                            
                        error = False
                        for s in sargs:
                            if(s.startswith('-j ') or s.startswith('--jobname ')):
                                s_split = s.strip().split()
                                if(len(s_split) >= 2):
                                    jobname = ' '.join(s_split[1:])
                            elif(s.startswith('-c ') or s.startswith('--cores ')):
                                s_split = s.strip().split()

                                if(len(s_split) == 2):
                                    if(s_split[1].isdigit()):
                                        cores = int(s_split[1])
                                    else:
                                        error = True
                                else:
                                    error = True
                            elif(s.startswith('-n ') or s.startswith('--nodes ')):
                                s_split = s.strip().split()

                                if(len(s_split) == 2):
                                    if(s_split[1].isdigit()):
                                        nodes = int(s_split[1])
                                    else:
                                        error = True
                                else:
                                    error = True
                            elif(s.startswith('-i') or s.startswith('--jobid ')):
                                continue
                            elif(s.startswith('-d ') or s.startswith('--depend ')):
                                s_split = s.strip().split()
                                if(len(s_split) == 2):
                                    depend = s_split[1].strip()
                                    s_dep = depend.split(',')

                                    for d in s_dep:
                                        if(not d.isdigit()):
                                            error = True
                                else:
                                    error = True
                            else:
                                error = True

                        if(error):
                            print('Error parse special syntax line: {}'.format(l), file=sys.stderr)
                            print('Review file: {}'.format(args.TASKFILE), file=sys.stderr)
                            exit(1)
                    else:
                        print('Error parse special syntax line: {}'.format(l), file=sys.stderr)
                        print('Review file: {}'.format(args.TASKFILE), file=sys.stderr)
                        exit(2)
                else:
                    if(len(l) > 0 and not l.startswith('#')):
                        t = l.split()

                        if(len(t) == 1):
                            t.append("$HOME")
                        elif(len(t) > 2):
                            print('Error parse command line: {}'.format(l), file=sys.stderr)
                            print('First field should be the command and optional second field should be working directory.', file=sys.stderr)
                            print('Review file: {}'.format(args.TASKFILE), file=sys.stderr)
                            exit(2)
                    
                        tasks.append(t)


                l = f.readline()

    except Exception as e:
        print('Error reading task file: {}'.format(args.TASKFILE),file=sys.stderr)
        logging.error('User: {}, Error reading task file: {}. Error: {}'.format(os.getlogin(), args.TASKFILE, e))
        exit(3)
    
    if(args.cores):
        cores = args.cores
    if(args.nodes):
        nodes = args.nodes
        
    if(cores == -1  or nodes == -1):
        print('''Could not find --cores or --nodes,
nether in {} in special syntax line nor on commandline.
Both --cores and --nodes must be provided.
Please try -h or --help for help.'''.format(args.TASKFILE), file=sys.stderr)
        exit(4)

    if(args.depend):
        s_dep = args.depend.split(',')
        
        for d in s_dep:
            if(not d.isdigit()):
                print('Error --depend must be comma separated list of jobids (integers).', file=sys.stderr)
                print('Try -h or --help for help.', file=sys.stderr)
                exit(5)

        depend = args.depend
        
    if(len(tasks) == 0):
        print('Error did not find any tasks in file: {}, will not add a job.'.format(args.TASKFILE), file=sys.stderr)
        exit(5)
    else:
        if(not args.jobid):
            print('Found {} number of tasks.'.format(len(tasks)))
        
    data = init_validate(args.conf.validate_file)
    data['action'] = L3QAction.ADD_PARA
    data['tasks'] = tasks
    data['user'] = pwd.getpwuid(os.getuid()).pw_name
    data['name'] = jobname
    data['cores'] = cores
    data['nodes'] = nodes
    data['depend'] = depend
    
    if(not args.jobid):
        print('Adding parallel job to l3q daemon ...')

    response = send_to_daemon(args.conf.l3qd_host, args.conf.l3qd_port, data)
    
    if(response.get('action') == L3QAction.ADD_PARA):
        if('error' in response):
            print('Error adding parallel job to l3q daemon', file=sys.stderr)
            print(response.get('error'), file=sys.stderr)
        elif('response' in response and 'response_id' in response):
            if(args.jobid):
                print(response.get('response_id'))
            else:
                print(response.get('response'))
        else:
            print('Error adding parallel job to l3q daemon', file=sys.stderr)
            logging.error('User: {}, Error adding parallel job to l3q daemon. Wrong keys in answer: {}'.format(os.getlogin(), response))
    else:
        print('Error adding parallel job to l3q daemon', file=sys.stderr)
        logging.error('User: {}, Error adding parallel job to l3q daemon. Wrong action in answer, action: {}'.format(os.getlogin(), response))



def process_add_seq(args: argparse.Namespace) -> None:
    '''
    Sub-command process add seq
    Command entered: l3q add seq <args>
    '''
    if(not os.path.exists(args.TASKFILE)):
        print('Error task file: {} does not exist.'.format(args.TASKFILE), file=sys.stderr)
        exit(1)

    tasks = []
    jobname = ''
    depend = ''
    try:
        with open(args.TASKFILE) as f:
            l = f.readline()
            while(len(l) > 0):
                l =l.strip()

                if(l.startswith('#--add-seq')):
                    iname = l.find('--jobname')
                    if(iname == -1):
                        iname = l.find('-j')

                    ijobid = l.find('--jobid')
                    if(ijobid == -1):
                        ijobid = l.find('-i')

                    idepend = l.find('--depend')
                    if(idepend == -1):
                        idepend = l.find('-d')

                    iargs = [iname, ijobid, idepend]
                    iargs.sort()

                    sargs =[]
                    i = 0
                    while(i<=2):
                        if(iargs[i] != -1):
                            if(i!=2):
                                sargs.append(l[iargs[i]:iargs[i+1]])
                            else:
                                sargs.append(l[iargs[i]:])
                        i += 1

                    error = False
                    for s in sargs:
                        if(s.startswith('-j ') or s.startswith('--jobname ')):
                            s_split = s.strip().split()
                            if(len(s_split) >= 2):
                                jobname = ' '.join(s_split[1:])
                        elif(s.startswith('-i') or s.startswith('--jobid')):
                            args.jobid = True
                        elif(s.startswith('-d ') or s.startswith('--depend ')):
                            s_split = s.strip().split()

                            if(len(s_split) == 2):
                                depend = s_split[1].strip()
                                s_dep = depend.split(',')

                                for d in s_dep:
                                    if(not d.isdigit()):
                                        error = True
                            else:
                                error = True
                        else:
                            error = True

                    if(error):
                        print('Error parse special syntax line: {}'.format(l), file=sys.stderr)
                        print('Review file: {}'.format(args.TASKFILE), file=sys.stderr)
                        exit(1)
                elif(len(l) > 0 and not l.startswith('#')):
                    t = l.split()

                    if(len(t) == 1):
                        t.append("$HOME")
                    elif(len(t) > 2):
                        print('Error parse command line: {}'.format(l), file=sys.stderr)
                        print('First field should be the command and optional second field should be working directory.', file=sys.stderr)
                        print('Review file: {}'.format(args.TASKFILE), file=sys.stderr)
                        exit(2)
                    
                    tasks.append(t)

                l = f.readline()
        
    except Exception as e:
        print('Error reading task file: {}'.format(args.TASKFILE),file=sys.stderr)
        logging.error('User: {}, Error reading task file: {}. Error: {}'.format(os.getlogin(), args.TASKFILE, e))
        exit(3)

    if(len(tasks) == 0):
        print('Error did not find any tasks in file: {}, will not add a job.'.format(args.TASKFILE), file=sys.stderr)
        exit(4)
    else:
        if(not args.jobid):
            print('Found {} number of tasks.'.format(len(tasks)))

    if(args.depend):
        s_dep = args.depend.split(',')
        
        for d in s_dep:
            if(not d.isdigit()):
                print('Error --depend must be comma separated list of jobids (integers).', file=sys.stderr)
                print('Try -h or --help for help.', file=sys.stderr)
                exit(5)

        depend = args.depend

            
    data = init_validate(args.conf.validate_file)
    data['action'] = L3QAction.ADD_SEQ
    data['tasks'] = tasks
    data['user'] = pwd.getpwuid(os.getuid()).pw_name
    data['depend'] = depend
    if(args.jobname):
        data['name'] = args.jobname
    else:
        data['name'] = jobname

    if(not args.jobid):
        print('Adding sequence job to l3q daemon ...')

    response = send_to_daemon(args.conf.l3qd_host, args.conf.l3qd_port, data)
    
    if(response.get('action') == L3QAction.ADD_SEQ):
        if('error' in response):
            print('Error adding sequence job to l3q daemon', file=sys.stderr)
            print(response.get('error'), file=sys.stderr)
        elif('response' in response and 'response_id' in response):
            if(args.jobid):
                print(response.get('response_id'))
            else:
                print(response.get('response'))
        else:
            print('Error adding sequence job to l3q daemon', file=sys.stderr)
            logging.error('User: {}, Error adding sequence job to l3q daemon. Wrong keys in answer: {}'.format(os.getlogin(), response))
    else:
        print('Error adding sequence job to l3q daemon', file=sys.stderr)
        logging.error('User: {}, Error adding sequence job to l3q daemon. Wrong action in answer, action: {}'.format(os.getlogin(), response))


               
def process_show_queue(args: argparse.Namespace) -> None:
    '''
    Sub-command process show queue
    Command entered: l3q show queue <args>
    '''

    data = init_validate(args.conf.validate_file)
    data['action'] = L3QAction.GET_QUEUE

    response = send_to_daemon(args.conf.l3qd_host, args.conf.l3qd_port, data)
    
    if(response.get('action') == L3QAction.GET_QUEUE):
        if('error' in response):
            print('Error retrieving queue from l3q daemon', file=sys.stderr)
            print(response.get('error'), file=sys.stderr)
        elif('queue' in response):
            if(args.long):
                headline = '=' * 175
                line = '-' * 175
                print(headline)
                sfrmt='{:6s} | {:20s} | {:20s} | {:^19s} | {:^19s} | {:^11s} | {:^11} | {:^16s} | {:10s} | {:^10s} | {}'
                print(sfrmt.format('Jobid', 'User', 'Name', 'Init', 'Start', 'Cores alloc', 'Nodes alloc', 'Tasks R/T/E:N', 'State', 'Depend', 'Nodes'))
                print(headline)

                for n in response.get('queue'):
                    if(n.get('nodes')):
                        nodes = n.get('nodes')
                    else:
                        nodes = '-'

                    if(len(n.get('depend')) == 0):
                        depend = '-'
                    else:
                        depend = n.get('depend')
                    print(sfrmt.format(str(n.get('jobid')), n.get('user'), n.get('name'),
                                       n.get('init_date'), n.get('start_date'), str(n.get('cores_alloc')),
                                       str(n.get('hosts_alloc')), n.get('tasks'), n.get('state'), depend, nodes))
                    print(line)
            else:
                headline = '=' * 121
                line = '-' * 121
                print(headline)
                sfrmt='{:6s} | {:20s} | {:20s} | {:^11s} | {:^11s} | {:^16s} | {:10s} | {:^5s}'
                print(sfrmt.format('Jobid', 'User', 'Name', 'Cores alloc', 'Nodes alloc', 'Tasks R/T/E:N', 'State', 'Nodes'))
                print(headline)

                for n in response.get('queue'):
                    if(n.get('nodes')):
                       nodes = n.get('nodes')
                    else:
                       nodes = '-'
                    print(sfrmt.format(str(n.get('jobid')), n.get('user'), n.get('name'), str(n.get('cores_alloc')),
                                       str(n.get('hosts_alloc')), n.get('tasks'), str(n.get('state')), nodes))
                    print(line)

        else:
            print('Error retrieving queue from l3q daemon', file=sys.stderr)
            logging.error('User: {}, Error retrieving queue from l3q daemon. Wrong keys in answer: {}'.format(os.getlogin(), response))
    else:
        print('Error retrieving queue from l3q daemon', file=sys.stderr)
        logging.error('User: {}, Error retrieving queue from l3q daemon. Wrong action in answer, action: {}'.format(os.getlogin(), response))



def process_show_history(args: argparse.Namespace) -> None:
    '''
    Sub-command process show history
    Command entered: l3q show history <args>
    '''

    data = init_validate(args.conf.validate_file)
    data['action'] = L3QAction.GET_HISTORY

    if(args.number != None):

        if(args.number <= 0):
            print('usage: l3q show history [-h] [-l] [-n NUMBER]', file=sys.stderr)
            print('l3q show history: error: argument --number must be a positive integer.', file=sys.stderr)
            exit(3)
        data['number'] = args.number
    
    response = send_to_daemon(args.conf.l3qd_host, args.conf.l3qd_port, data)
    
    if(response.get('action') == L3QAction.GET_HISTORY):
        if('error' in response):
            print('Error retrieving history from l3q daemon', file=sys.stderr)
            print(response.get('error'), file=sys.stderr)
        elif('history' in response):
            if(args.long):
                headline = '=' * 197
                line = '-' * 197
                print(headline)
                sfrmt='{:6s} | {:20s} | {:20s} | {:^19s} | {:^19s} | {:^19s} | {:^11s} | {:^11s} | {:^16s} | {:10s} | {:^10s} | {}'
                print(sfrmt.format('Jobid', 'User', 'Name', 'Init', 'Start', 'End', 'Cores alloc', 'Nodes alloc', 'Tasks R/T/E:N', 'State', 'Depend', 'Nodes'))
                print(headline)

                for n in response.get('history'):
                    if(n.get('nodes')):
                       nodes = n.get('nodes')
                    else:
                       nodes = '-'

                    if(len(n.get('depend')) == 0):
                        depend = '-'
                    else:
                        depend = n.get('depend')
                        
                    print(sfrmt.format(str(n.get('jobid')), n.get('user'), n.get('name'),
                                       n.get('init_date'), n.get('start_date'), n.get('end_date'), str(n.get('cores_alloc')),
                                       str(n.get('hosts_alloc')), n.get('tasks'), n.get('state'), depend, nodes))
                    print(line)
            else:
                headline = '=' * 121
                line = '-' * 121
                print(headline)
                sfrmt='{:6s} | {:20s} | {:20s} | {:^11s} | {:^11s} | {:^16s} | {:10s} | {:^5s}'
                print(sfrmt.format('Jobid', 'User', 'Name', 'Cores alloc', 'Nodes alloc', 'Tasks R/T/E:N', 'State', 'Nodes'))
                print(headline)

                for n in response.get('history'):
                    if(n.get('nodes')):
                       nodes = n.get('nodes')
                    else:
                       nodes = '-'
                    print(sfrmt.format(str(n.get('jobid')), n.get('user'), n.get('name'), str(n.get('cores_alloc')),
                                       str(n.get('hosts_alloc')), n.get('tasks'), str(n.get('state')), nodes))
                    print(line)

        else:
            print('Error retrieving history from l3q daemon', file=sys.stderr)
            logging.error('User: {}, Error retrieving history from l3q daemon. Wrong keys in answer: {}'.format(os.getlogin(), response))
    else:
        print('Error retrieving history from l3q daemon', file=sys.stderr)
        logging.error('User: {}, Error retrieving history from l3q daemon. Wrong action in answer, action: {}'.format(os.getlogin(), response))



def process_show_job(args: argparse.Namespace) -> None:
    '''
    Sub-command process show job
    Command entered: l3q show job <args>
    '''

    data = init_validate(args.conf.validate_file)
    data['action'] = L3QAction.GET_JOB_INFO
    data['jobid'] = args.JOBID
    data['details'] = args.details

    response = send_to_daemon(args.conf.l3qd_host, args.conf.l3qd_port, data)
    
    if(response.get('action') == L3QAction.GET_JOB_INFO):
        if('error' in response):
            print('Error retrieving job with jobid: {} from l3q daemon'.format(args.JOBID), file=sys.stderr)
            print(response.get('error'), file=sys.stderr)
        elif('jobid' in response):
            print('Jobid:           {}'.format(response.get('jobid')))
            print('Name:            {}'.format(response.get('name')))
            print('User:            {}'.format(response.get('user')))
            print('State:           {}'.format(response.get('state')))
            print('Job type:        {}'.format(response.get('type')))
            if(len(response.get('depend')) > 0):
                print('Depend:          {}'.format(response.get('depend')))
            print('')
            print('Initialize date: {}'.format(response.get('init_date')))
            print('Start date:      {}'.format(response.get('start_date')))
            print('End date:        {}'.format(response.get('end_date')))
            if(args.details):
                print('Run time:        {}'.format(response.get('runtime')))
            print('')
            print('Nodes: {}'.format(response.get('nodes')))
            print('Number of node:                     {}'.format(response.get('hosts_alloc')))
            print('Number of allocated cores per node: {}'.format(response.get('cores_alloc')))
            print('Number of tasks running:            {}'.format(response.get('no_tasks_running')))
            print('Number of tasks terminated:         {}'.format(response.get('no_tasks_finished')))
            print('Number of error tasks:              {}'.format(response.get('no_tasks_err')))
            print('')
            print('Total number of tasks:              {}'.format(len(response.get('tasks'))))
            print('')
            print('')
            print('')
            print('Tasks:')

            for t in response.get('tasks'):
                print('Taskid:            {}'.format(t.get('taskid')))
                print('Command:           {}'.format(t.get('command')))
                if(args.details):
                    print('Working directory: {}'.format(t.get('workdir')))
                print('Node:              {}'.format(t.get('node')))
                print('Exit code:         {}'.format(t.get('exit_code')))
                print('Start date:        {}'.format(t.get('start_date')))                
                if(args.details):
                    print('End date:          {}'.format(t.get('end_date')))
                    print('Run time:          {}'.format(t.get('runtime')))
                    print('\nOutput standard out:\n{}'.format(t.get('stdout')))
                    print('\nOutput standard error:\n{}'.format(t.get('stderr')))
                print('')
                print('')
        else:
            print('Error retrieving job with jobid: {} from l3q daemon'.format(args.JOBID), file=sys.stderr)
            logging.error('User: {}, Error retrieving job with jobid: {} from l3q daemon. Wrong keys in answer: {}'.format(os.getlogin(), args.JOBID, response))
    else:
        print('Error retrieving job with jobid: {} from l3q daemon'.format(args.JOBID), file=sys.stderr)
        logging.error('User: {}, Error retrieving job with jobid: {} from l3q daemon. Wrong action in answer, action: {}'.format(os.getlogin(), args.JOBID, response))


def process_show_task(args: argparse.Namespace) -> None:
    '''
    Sub-command process show task
    Command entered: l3q show task <args>
    '''

    data = init_validate(args.conf.validate_file)
    data['action'] = L3QAction.GET_TASK_INFO
    data['taskid'] = args.TASKID

    response = send_to_daemon(args.conf.l3qd_host, args.conf.l3qd_port, data)
    
    if(response.get('action') == L3QAction.GET_TASK_INFO):
        if('error' in response):
            print('Error retrieving task with taskid: {} from l3q daemon'.format(args.TASKID), file=sys.stderr)
            print(response.get('error'), file=sys.stderr)
        elif('taskid' in response):
            print('Taskid:            {}'.format(response.get('taskid')))
            print('Part of job:       {}'.format(response.get('jobid')))
            print('Node:              {}'.format(response.get('node')))
            print('Systemd unit:      {}'.format(response.get('unitname')))
            print('Command:           {}'.format(response.get('command')))
            print('Working directory: {}'.format(response.get('workdir')))
            print('Active state:      {}'.format(response.get('active_state')))
            print('Sub state:         {}'.format(response.get('sub_state')))
            print('Memory peak:       {}'.format(response.get('memory_peak')))
            print('Exit code:         {}'.format(response.get('exit_code')))
            print('Start date:        {}'.format(response.get('start_date')))
            print('End date:          {}'.format(response.get('end_date')))
            print('Run time:          {}'.format(response.get('runtime')))
            print('\nOutput standard out:\n{}'.format(response.get('stdout')))
            print('\nOutput standard error:\n{}'.format(response.get('stderr')))
            print('')
        else:
            print('Error retrieving task with taskid: {} from l3q daemon'.format(args.TASKID), file=sys.stderr)
            logging.error('User: {}, Error retrieving job with jobid: {} from l3q daemon. Wrong keys in answer: {}'.format(os.getlogin(), args.JOBID, response))
    else:
        print('Error retrieving task with taskid: {} from l3q daemon'.format(args.TASKID), file=sys.stderr)
        logging.error('User: {}, Error retrieving task with taskid: {} from l3q daemon. Wrong action in answer, action: {}'.format(os.getlogin(), args.TASKID, response))

        

def human_byte_str(val: int) -> str:
    '''
    Takes an int as argument and returns
    a string containing unit suffix and value.
    Eg, B, kB, MB, GB, TB
    '''

    no = 0
    byte = float(val)
    while True:
        pre_byte = byte
        byte = byte/1024.0

        if byte > 1.0:
            no += 1
        else:
            if(no == 0):
                return "{:.2f} B".format(pre_byte)
            elif(no == 1):
                return "{:.2f} kB".format(pre_byte)
            elif(no == 2):
                return "{:.2f} MB".format(pre_byte)
            elif(no == 3):
                return "{:.2f} GB".format(pre_byte)
            elif(no >= 4):
                return "{:.2f} TB".format(pre_byte)

        
def process_show_status(args: argparse.Namespace) -> None:
    '''
    Sub-command process show status
    Command entered: l3q show status <args>
    '''

    data = init_validate(args.conf.validate_file)
    data['action'] = L3QAction.GET_NODE_STATUS

    response = send_to_daemon(args.conf.l3qd_host, args.conf.l3qd_port, data)
    
    if(response.get('action') == L3QAction.GET_NODE_STATUS):
        if('error' in response):
            print('Error retrieving node status from l3q daemon', file=sys.stderr)
            print(response.get('error'), file=sys.stderr)
        elif('nodes' in response):
            headline = '=' * 150
            line = '-' * 150
            print(headline)
            sfrmt='{:20s} | {:^11s} | {:^11s} | {:^11s} | {:^12s} | {:^12s} |  {:^12s} | {:^12s} | {:21s}'
            print(sfrmt.format('Node name', 'Cores', 'Cores alloc', 'Cores used', 'RAM', 'RAM used', 'Swap', 'Swap used', 'State'))
            print(headline)

            for n in response.get('nodes'):
                if(args.human):
                    if(n.get('ram').isdigit()):
                        ram = human_byte_str(n.get('ram'))
                    else:
                        ram = n.get('ram')

                    if(n.get('ram_used').isdigit()):
                        ram_used = human_byte_str(n.get('ram_used'))
                    else:
                        ram_used = n.get('ram_used')

                    if(n.get('total_swap').isdigit()):
                        total_swap = human_byte_str(n.get('total_swap'))
                    else:
                        total_swap = n.get('total_swap')

                    if(n.get('used_swap').isdigit()):
                        used_swap = human_byte_str(n.get('used_swap'))
                    else:
                        used_swap = n.get('used_swap')
                        
                    print(sfrmt.format(n.get('name'), str(n.get('cores')), str(n.get('cores_alloc')),
                                       str(n.get('cores_in_use')), ram, ram_used, total_swap,
                                       used_swap, n.get('state')))
                else:
                    print(sfrmt.format(n.get('name'), str(n.get('cores')), str(n.get('cores_alloc')),
                                       str(n.get('cores_in_use')), n.get('ram'), n.get('ram_used'), n.get('total_swap'), n.get('used_swap'), n.get('state')))
                print(line)

        else:
            print('Error retrieving node status from l3q daemon', file=sys.stderr)
            logging.error('User: {}, Error retrieving node status from l3q daemon. Wrong keys in answer: {}'.format(os.getlogin(), response))
    else:
        print('Error retrieving node status from l3q daemon', file=sys.stderr)
        logging.error('User: {}, Error retrieving node status from l3q daemon. Wrong action in answer, action: {}'.format(os.getlogin(), response))



def process_list_task(args: argparse.Namespace) -> None:
    '''
    Sub-command process list task
    Command entered: l3q list task <args>
    '''

    data = init_validate(args.conf.validate_file)
    data['action'] = L3QAction.GET_TASK_LIST
    data['jobid'] = args.JOBID

    response = send_to_daemon(args.conf.l3qd_host, args.conf.l3qd_port, data)
    
    if(response.get('action') == L3QAction.GET_TASK_LIST):
        if('error' in response):
            print('Error retrieving tasks for jobid: {} from l3q daemon'.format(args.JOBID), file=sys.stderr)
            print(response.get('error'), file=sys.stderr)
        elif('jobid' in response and 'tasks' in response):


            headline = '=' * 140
            line = '-' * 140
            print(headline)
            sfrmt='{:6s} | {:20s} | {:^19s} | {:^19s} | {:^19s} | {:^9s} | {}'
            print(sfrmt.format('Taskid', 'Node', 'Start', 'End', 'Run time', 'Exit code', 'Command'))
            print(headline)


            
            for t in response.get('tasks'):
                print(sfrmt.format(str(t.get('taskid')), t.get('node'), 
                                   t.get('start_date'), t.get('end_date'),
                                   t.get('runtime'), str(t.get('exit_code')),
                                   t.get('command')))
                print(line)
            print('')
        else:
            print('Error retrieving tasks for jobid: {} from l3q daemon'.format(args.JOBID), file=sys.stderr)
            logging.error('User: {}, Error retrieving tasks for jobid: {} from l3q daemon. Wrong keys in answer: {}'.format(os.getlogin(), args.JOBID, response))
    else:
        print('Error retrieving tasks for jobid: {} from l3q daemon'.format(args.JOBID), file=sys.stderr)
        logging.error('User: {}, Error retrieving tasks for jobid: {} from l3q daemon. Wrong action in answer, action: {}'.format(os.getlogin(), args.JOBID, response))



        
def process_cancel_job(args: argparse.Namespace) -> None:
    '''
    Sub-command process cancel job
    Command entered: l3q cancel job <args>
    '''

    data = init_validate(args.conf.validate_file)
    data['action'] = L3QAction.CANCEL_JOB
    data['jobid'] = args.JOBID
    data['user'] = pwd.getpwuid(os.getuid()).pw_name

    response = send_to_daemon(args.conf.l3qd_host, args.conf.l3qd_port, data)
    
    if(response.get('action') == L3QAction.CANCEL_JOB):
        if('error' in response):
            print('Error cancel job with jobid: {} from l3q daemon'.format(args.JOBID), file=sys.stderr)
            print(response.get('error'), file=sys.stderr)
        elif('response' in response):
            print('{}'.format(response.get('response')))
        else:
            print('Error cancel job with jobid: {} from l3q daemon'.format(args.JOBID), file=sys.stderr)
            logging.error('User: {}, Error cancel job with jobid: {} from l3q daemon. Wrong keys in answer: {}'.format(os.getlogin(), args.JOBID, response))
    else:
        print('Error cancel job with jobid: {} from l3q daemon'.format(args.JOBID), file=sys.stderr)
        logging.error('User: {}, Error cancel job with jobid: {} from l3q daemon. Wrong action in answer, action: {}'.format(os.getlogin(), args.JOBID, response))




def conf_node_set(args: argparse.Namespace) -> None:
    '''
    Sub-command conf node set
    Command entered: l3q node set <args>
    User must be root.
    '''

    if(not is_root()):
        print('Permission denied.', file=sys.stderr)
        exit(1)

    if(not args.online and not args.offline):
        print('l3q node set: error: one of the following arguments are required: --online or --offline', file=sys.stderr)
        exit(2)
    elif(args.online and args.offline):
        print('l3q node set: error: both arguments can not be present: --online and --offline', file=sys.stderr)
        exit(2)

    data = init_validate(args.conf.validate_file)

    if(args.online):
        data['action'] = L3QAction.SET_NODE_ONLINE
    elif(args.offline):
        data['action'] = L3QAction.SET_NODE_OFFLINE

    data['node_name'] =  args.NODENAME
    

    response = send_to_daemon(args.conf.l3qd_host, args.conf.l3qd_port, data)
    
    if(response.get('action') == L3QAction.SET_NODE_ONLINE or response.get('action') == L3QAction.SET_NODE_OFFLINE):
        if('error' in response):
            print('Error changing state on node: {} in l3q daemon'.format(args.NODENAME), file=sys.stderr)
            print(response.get('error'), file=sys.stderr)
        elif('node' in response):
            print(response.get('node'))
        else:
            print('Error changing state on node: {} in l3q daemon'.format(args.NODENAME), file=sys.stderr)
            logging.error('User: {}, Error changing state on node: {} in l3q daemon. Wrong keys in answer: {}'.format(os.getlogin(), args.NODENAME, response))
    else:
        print('Error changing state on node: {} in l3q daemon'.format(args.NODENAME), file=sys.stderr)
        logging.error('User: {}, Error changing state on node: {} in l3q daemon. Wrong action in answer, action: {}'.format(os.getlogin(), args.NODENAME, response))


        
def conf_validate(args: argparse.Namespace) -> None:
    '''
    Sub-command conf validate
    Command entered: l3q validate <args>
    User must be root.
    '''

    if( not is_root()):
        print('Permission denied.', file=sys.stderr)
        exit(1)

    print('Please make sure that you have started the validation daemon')
    print('on the same server as the l3q daemon runs.')
    print('Start validation daemon with command:')
    print('l3qd --validate-host')
    print('')
    host = os.uname().nodename
    hostname = input('Specify hostname (press enter for default: {}): '.format(host))
    if(len(hostname.strip()) == 0):
        hostname = host
    else:
        hostname = hostname.strip()
        
    pw = getpass.getpass('Enter password, same as entered on l3q validation daemon: ')

    data = {'action': L3QAction.VALIDATE_KEY, 'password': pw, 'hostname': hostname }

    response = send_to_daemon(args.conf.l3qd_host, args.port, data)

    if(response.get('action') == L3QAction.VALIDATE_KEY):
        if('error' in response):
            print('Error setting up validate key:', file=sys.stderr)
            print(response.get('error'), file=sys.stderr)
        elif('key' in response):
            # save key to file
            if(not os.path.exists(os.path.dirname(args.conf.validate_file))):
                os.mkdir(os.path.dirname(args.conf.validate_file))
                shutil.chown(os.path.dirname(args.conf.validate_file), user='root', group=args.conf.group)
            
            with open(args.conf.validate_file, 'w') as f:
                f.write('{}\n'.format(response['key']))

            shutil.chown(args.conf.validate_file, user='root', group=args.conf.group)
            os.chmod(args.conf.validate_file, L3QConfig.validate_mode - 0o100000)
            print('Validation SUCCESS!!')
                
        else:
            print('Error setting up validate key.', file=sys.stderr)
            logging.error('User: {}, Error setting up validate key. Wrong keys in answer: {}'.format(os.getlogin(), response))
    else:
        print('Error setting up validate key.', file=sys.stderr)
        logging.error('User: {}, Error setting up validate key. Wrong action in answer, action: {}'.format(os.getlogin(), response.get('action')))


def is_root() -> bool:
    '''
    Returns True if user is root.
    '''

    usr = pwd.getpwuid(os.getuid())
    return usr[0] == 'root' and usr[2] == 0


def init_validate(validate_file: str) -> dict[str, Union[str, list[str], int]]:
    '''
    Reads key from validate file,
    only reads first line.
    Returns dict with 'key' set to
    string from file.
    '''

    key = ''

    try:
        with open(validate_file) as f:
            key = f.readline().strip()
    except Exception as e:
        print('Error reading validate file: {}'.format(validate_file), file=sys.stderr)
        logging.error('User: {}, Error reading validate file: {}, Error: {}'.format(os.getlogin(), validate_file, e))
        exit(1)

    return {'key': key}



def send_to_daemon(hostname: str, port: int, action_data: Mapping[str, str|list[str]|int]) -> Any:
    '''
    Function will make a post to host and port
    with the action_data as message.
    Hostname is the host where the l3q daemon 
    is run.
    Returns connection error or response message
    from l3q daemon.
    action_data must be an dict with an "action"
    field containing a L3QAction, the rest of the 
    dict is data required for action.
    '''

    try:
        json_data = json.dumps(action_data)
    except Exception as e:
        print('Error encoding data into json string', file=sys.stderr)
        logging.error('User: {}, Error encoding data into json string:\n{}'.format(os.getlogin(), e))
        exit(2)
    try:
        response = requests.post('https://' + hostname + ':' + str(port), data=json_data, verify=False)
        response.raise_for_status()
    except Exception as e:
        print('Error connecting to l3q daemon with URL: https://{}'.format(hostname + ':' + str(port)), file=sys.stderr)
        logging.error('User: {}, Error connecting to l3q daemon with URL: https://{}\n{}'.format(os.getlogin(), hostname + ':' + str(port), e))
        exit(3)

    try:
        res = json.loads(response.text)
    except Exception as e:
        print('Error decoding data from json string', file=sys.stderr)
        logging.error('User: {}, Error decoding data from json string:\n{}'.format(os.getlogin(), e))
        exit(4)
        
    return res

    
    
def main() -> None:
    '''Main function, where program starts'''

    conf: L3QConfig = L3QConfig(parse_validate=False)
    init_log: bool = True
    
    a = sys.argv
    if(len(a) == 2 and a[1] == '--version'):
        init_log = False
    elif('--help' in a or '-h' in a):
        init_log = False
    elif(len(a) >= 2 and a[1] == 'validate'):
        '''command: l3q validate ...'''
        conf = L3QConfig(parse_validate=False)
    else:
        conf = L3QConfig()
    
    parser = argparse.ArgumentParser(
        prog='l3q',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description='''This is the client program
for l3q that interacts with the l3q daemon.
This tool is used to get status, add jobs and
configure the current l3q system.
If called without arguments the queue will be printed, same as: l3q show queue''',
        epilog=textwrap.dedent('''In l3q a job is a complete unit that is queued
and executed as one unit.
A task is one program that is executed inside a job that contains multiple
tasks.
    
Additional help can be found for each sub-command with the flag -h or --help.
Example:
l3q show --help, will describe the show sub-command
l3q add --help, will describe the add sub-command 
l3q add seq --help, will describe the seq sub-command of add sub-command.

More information is available in man pages:
l3q(1), l3q.conf(5), l3qd(8), l3qd.conf(5)'''))
    parser.add_argument(
        '--version',
        action='store_true',
        help='''Prints version and exit''')
    parser.add_argument('-l', '--long', action='store_true',
                                      help='''Prints a long and more detailed view of the queue''')

    subparser = parser.add_subparsers(help='sub-commands')
    parser.set_defaults(func=process_show_queue, conf=conf)
    
    # sub-command process-add
    parser_process_add = subparser.add_parser('add', help='Adds jobs to l3q',
                                                      formatter_class=argparse.RawTextHelpFormatter,
                                                      description='''Adds a new job to l3q''')
    parser_process_add.set_defaults(func=wrong_args)
    subparser_process_add = parser_process_add.add_subparsers(help='add sub-commands')
    parser_process_add.add_argument('-t', '--taskfile', help='''Path to file containing all tasks that should be run in sequence or parallel.
One task per line, empty lines and lines starting with # are ignored.
TASKFILE must contain special syntax line for either
parallel or squence job.

Special syntax in TASKFILE:
#--add-para ...
#--add-seq ...''')
    parser_process_add.add_argument('-i', '--jobid', action='store_true',
                                    help='''Jobid of this job will be returned on success
as the only output.
On failure the error message is printed.''')
    parser_process_add.add_argument('-d', '--depend', help='''Comma separated list of other jobid that this job depends on.
All jobs specified in this joblist have to terminate without 
errors before this job is started. If a depenedent job fails 
this job will also fail and will not be started.
Example: 1,5,9 (comma separated without whitespaces)''')

    parser_process_add.set_defaults(func=process_add)
    
    #process-add sub-commands
    parser_process_add_para = subparser_process_add.add_parser('para', help='Adds a parallel job of single threaded tasks to l3q',
                                                formatter_class=argparse.RawTextHelpFormatter,
                                                description='''Adds a new parallel job of single threaded tasks to l3q. 
Number of tasks executed in parallel depends on number of cores and hosts specified''',
                                                epilog='''If TASKFILE only contains one task on each line then both options --cores and --nodes are required.
If TASKFILE contains a line with the special syntax specifying required options then no options are required on the command line.
If TASKFILE contains the special syntax line and --core and --nodes are given as arguments these options will be used
and the special syntax line will be ignored.

Special syntax in TASKFILE:
#--add-para --cores 5 --nodes 8''')
    parser_process_add_para.add_argument('-c', '--cores', type=int, help='Number of cores to use on each node, --nodes is also required.')
    parser_process_add_para.add_argument('-n', '--nodes', type=int, help='Number of nodes to use, l3q will choose which nodes to use. --cores is also required.')
    parser_process_add_para.add_argument('TASKFILE', help='''Path to file containing all tasks that should be run in parallel.
One task per line, empty lines and lines starting with # are ignored.''')
    parser_process_add_para.add_argument('-j', '--jobname', help='''Name of specifed job.''')
    parser_process_add_para.add_argument('-i', '--jobid', action='store_true',
                                    help='''Jobid of this job will be returned on success
as the only output.
On failure the error message is printed.''')
    parser_process_add_para.add_argument('-d', '--depend', help='''Comma separated list of other jobid that this job depends on.
All jobs specified in this joblist have to terminate without 
errors before this job is started. If a depenedent job fails 
this job will also fail and will not be started.
Example: 1,5,9 (comma separated without whitespaces)''')

    
    parser_process_add_para.set_defaults(func=process_add_para)
    
    parser_process_add_seq = subparser_process_add.add_parser('seq', help='Adds a sequential job of single threaded tasks to l3q.',
                                                              formatter_class=argparse.RawTextHelpFormatter,
                                                              description='''Adds a new sequential job of single threaded tasks to l3q. 
All tasks will be executed one after another.''',
                                                              epilog='''If TASKFILE contains a line with the special syntax specifying the name of the task this name will be used.
If TASKFILE contains the special syntax line and --jobname is given as arguments this options will be used
and the special syntax line will be ignored.
If TASKFILE contains multiple special syntax lines the last line will be used.

Special syntax in TASKFILE:
#--add-seq --jobname The name of the job''')
    parser_process_add_seq.add_argument('TASKFILE', help='''Path to file containing all tasks that should be run in sequence.
One task per line, empty lines and lines starting with # are ignored.''')
    parser_process_add_seq.add_argument('-j', '--jobname', help='''Name of specifed job.''')
    parser_process_add_seq.add_argument('-i', '--jobid', action='store_true',
                                    help='''Jobid of this job will be returned on success
as the only output.
On failure the error message is printed.''')
    parser_process_add_seq.add_argument('-d', '--depend', help='''Comma separated list of other jobid that this job depends on.
All jobs specified in this joblist have to terminate without 
errors before this job is started. If a depenedent job fails 
this job will also fail and will not be started.
Example: 1,5,9 (comma separated without whitespaces)''')

    
    parser_process_add_seq.set_defaults(func=process_add_seq, conf=conf)
    
    
    # sub-command process-show
    parser_process_show = subparser.add_parser('show', help='Shows different type of info and status of l3q')
    parser_process_show.set_defaults(func=wrong_args)
    subparser_process_show = parser_process_show.add_subparsers(help='show sub-commands')
    
    #process-show sub-commands
    parser_process_show_queue = subparser_process_show.add_parser('queue', help='Lists all running and queued jobs in l3q',
                                                                  formatter_class=argparse.RawDescriptionHelpFormatter,
                                                                  description='''Lists all running and queued jobs in l3q.
The values in Task column are the number of tasks: Running/Terminated/Error:Total''')
    parser_process_show_queue.set_defaults(func=process_show_queue, conf=conf)
    parser_process_show_queue.add_argument('-l', '--long', action='store_true',
                                      help='''Prints a long and more detailed view of the queue''')    
    parser_process_show_history = subparser_process_show.add_parser('history', help='Lists all terminated and canceled jobs in l3q',
                                                                    formatter_class=argparse.RawDescriptionHelpFormatter,
                                                                    description='''Lists all terminated and canceled jobs in l3q''')
    parser_process_show_history.add_argument('-l', '--long', action='store_true',
                                      help='''Prints a long and more detailed view of the history''')
    parser_process_show_history.add_argument('-n', '--number', type=int,
                                      help='''Limit the number of old jobs to be displayed.''')    
    parser_process_show_history.set_defaults(func=process_show_history, conf=conf)
    
    parser_process_show_job = subparser_process_show.add_parser('job', help='Shows information about specified job',
                                                                description='''Shows information about specified job''')
    parser_process_show_job.add_argument('-d', '--details', action='store_true',
                                      help='''Sows detailed information about specified job''')    
    parser_process_show_job.add_argument('JOBID', type=int, help='Id number of job to show')
    parser_process_show_job.set_defaults(func=process_show_job, conf=conf)

    parser_process_show_task = subparser_process_show.add_parser('task', help='Shows information about specified task',
                                                                description='''Shows information about specified task''')
    parser_process_show_task.add_argument('TASKID', type=int, help='Id number of task to show')
    parser_process_show_task.set_defaults(func=process_show_task, conf=conf)
    
    parser_process_show_status = subparser_process_show.add_parser('status', help='Shows information about all hosts available in l3q',
                                                                   description='''Shows information about all hosts available in l3q''')
    parser_process_show_status.add_argument('-H', '--human', action='store_true',
                                      help='''Printing values in human readable formats''')    
    parser_process_show_status.set_defaults(func=process_show_status, conf=conf)
    
    # sub-command process-list
    parser_process_list = subparser.add_parser('list', help='List different type of info and status of l3q')
    parser_process_list.set_defaults(func=wrong_args)
    subparser_process_list = parser_process_list.add_subparsers(help='list sub-commands')

    parser_process_list_task = subparser_process_list.add_parser('task', help='List all tasks for specified job',
                                                                description='''List all tasks for specified job''')
    parser_process_list_task.add_argument('JOBID', type=int, help='Id number of job to show tasks')
    parser_process_list_task.set_defaults(func=process_list_task, conf=conf)

    
    # sub-command process-cancel
    parser_process_cancel = subparser.add_parser('cancel', help='Cancel active operations in l3q')
    parser_process_cancel.set_defaults(func=wrong_args)
    subparser_process_cancel = parser_process_cancel.add_subparsers(help='cancel sub-commands')
    
    #process-cancel sub-commands
    parser_process_cancel_job = subparser_process_cancel.add_parser('job', help='Cancel specified running or queued job',
                                                                    description='''Cancel specified running or queued job''')
    parser_process_cancel_job.add_argument('JOBID', type=int, help='Id number of job to cancel')
    parser_process_cancel_job.set_defaults(func=process_cancel_job)
    
    #conf sub-commands
    parser_conf_node = subparser.add_parser('node', help='Configure compute nodes in l3q',
                                               description='''Configure compute nodes in l3q''')
    parser_conf_node.set_defaults(func=wrong_args)
    subparser_conf_node = parser_conf_node.add_subparsers(help='node sub-commands')
    
    parser_conf_validate = subparser.add_parser('validate', help='Configure and validate client with l3q daemon',
                                                     formatter_class=argparse.RawDescriptionHelpFormatter,
                                                     description='''Configure and validate client with l3q daemon.
Without validation the clients on this host will not be able to communicate with daemon.
Start the validation server on the same server as the l3q daemon with: 
l3qd --validate-host
and follow instructions.''')
    parser_conf_validate.add_argument('-p', '--port', type=int, required=True,
                                      help='Port that the validate server is listening on.')
    parser_conf_validate.set_defaults(func=conf_validate, conf=conf)
    
    
    parser_conf_node_set = subparser_conf_node.add_parser('set', help='Set status of a compute node in l3q',
                                                          formatter_class=argparse.RawTextHelpFormatter,
                                                          description='''Set status of a compute node in l3q. 
If set offline current running processes will run 
until finished before set offline and will not 
accept any new processes.''')
    parser_conf_node_set.add_argument('NODENAME', help='Name of node to change state on in the l3q daemon.')
    parser_conf_node_set.add_argument('--online', action='store_true',
                                      help='''Set node soft online and will display Soft Online
until node is reacheble from l3q daemon and changes to Online, 
if node is not reachable l3q daemon will set node offline again.''')
    parser_conf_node_set.add_argument('--offline', action='store_true',
                                      help='''Set node offline,
if node still run processes, these will finish before
set in Maintenance mode and Maintenance draining mode will show in status.
While in Maitenance mode the node will not run any new jobs.''')
    parser_conf_node_set.set_defaults(func=conf_node_set, conf=conf)
    
    if (init_log):
        try:
            logging.basicConfig(filename=conf.log_file, encoding='utf-8', level=logging.DEBUG,
                                format='%(asctime)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
        except Exception:
            print('Failed to open log: {}'.format(conf.log_file), file=sys.stderr)
            print('Check configuration of client tools', file=sys.stderr)
            exit(1)
        
    urllib3.disable_warnings(category=InsecureRequestWarning)

    args = parser.parse_args()

    if(args.version):
        version()
        exit(0)

    args.func(args)

if __name__ == '__main__':
    main()
